'=====================================================================
'
'  File:    ForEachDirectory.vb
'
'  Summary: This file contains the implementation of a custom foreach enumerator. It enumerates
'           directories, and provides their path's as strings. Optionally, it can be configured to 
'           recursively enumerate the subfolders of the folders in the specified root directory. The 
'           root directory is set using either direct input, a variable, or a connection manager. The user 
'           interface for the enumerator is implemented in the ForEachEnumeratorUI.cs file.
'
'  Date:    July 22, 2005
'
'---------------------------------------------------------------------
'  This file is part of the Microsoft SQL Server Code Samples.
'  Copyright (C) Microsoft Corporation.  All rights reserved.
'
'  This source code is intended only as a supplement to Microsoft
'  Development Tools andor on-line documentation.  See these other
'  materials for detailed information regarding Microsoft code samples.
'
'  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY ANDOR FITNESS FOR A
'  PARTICULAR PURPOSE.
'===================================================================== 
Imports System
Imports System.IO
Imports System.Xml
Imports System.Collections
Imports Microsoft.SqlServer.Dts.Runtime
Imports Microsoft.SqlServer.Dts.Runtime.Enumerators

<DtsForEachEnumerator(DisplayName:="For Each Directory VB", Description:="Enumerates directories, and optionally, subdirectories", UITypeName:="Microsoft.Samples.SqlServer.Dts.ForEachDirectoryUI,ForEachDirectoryVB,Version=1.0.0.0,Culture=Neutral,PublicKeyToken=e40289b2ee1f3c97")> _
Public Class ForEachDirectory
    Inherits ForEachEnumerator
    Implements IDTSComponentPersist

#Region "Members"
    Private directories As ArrayList
    Private rootDirectoryValue As String
    Private enumerateSubFoldersValue As Boolean
    Private includeRootDirectoryValue As Boolean
    Private siblingFoldersBeforeSubFoldersValue As Boolean
    Private myRootDirectorySourceValue As RootDirectorySource


    Public Enum RootDirectorySource
        DirectInput
        Variable
        ConnectionManager
    End Enum 'RootDirectorySource
#End Region

#Region "Properties"

    Public Property RootDirectory() As String
        Get
            Return Me.rootDirectoryValue
        End Get
        Set(ByVal Value As String)
            Me.rootDirectoryValue = Value
        End Set
    End Property

    Public Property EnumerateSubFolders() As Boolean
        Get
            Return Me.enumerateSubFoldersValue
        End Get
        Set(ByVal Value As Boolean)
            Me.enumerateSubFoldersValue = Value
        End Set
    End Property

    Public Property RootDirectorySourceValue() As RootDirectorySource
        Get
            Return Me.myRootDirectorySourceValue
        End Get
        Set(ByVal Value As RootDirectorySource)
            Me.myRootDirectorySourceValue = Value
        End Set
    End Property

    Public Property IncludeRootDirectory() As Boolean
        Get
            Return Me.includeRootDirectoryValue
        End Get
        Set(ByVal Value As Boolean)
            Me.includeRootDirectoryValue = Value
        End Set
    End Property

    Public Property SiblingFoldersBeforeSubFolders() As Boolean
        Get
            Return siblingFoldersBeforeSubFoldersValue
        End Get
        Set(ByVal Value As Boolean)
            siblingFoldersBeforeSubFoldersValue = Value
        End Set
    End Property
#End Region

#Region "Validate "

    ' <summary>
    ' This method is called by the runtime to allow the enumerator to verify that it is properly configured.
    ' The ForEachDirectory enumerator verifies that the RootDirectory value is specified. No other configuration is required.
    ' <summary>
    ' <param name="connections">The collection of ConnectionManagers in the package.<param>
    ' <param name="variableDispenser">A VariableDispenser used to read and write variables in the package.<param>
    ' <param name="infoEvents">Used to fire events.<param>
    ' <param name="log">Used to write log entries.<param>
    ' <returns>A value from the DTSExecResult indicating the success or failur of validation.
    ' If Failure is returned then the package will not execute.<returns>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
    Public Overrides Function Validate(ByVal connections As Connections, ByVal variableDispenser As VariableDispenser, ByVal infoEvents As IDTSInfoEvents, ByVal log As IDTSLogging) As DTSExecResult
        If (connections Is Nothing) Then
            Throw New ArgumentNullException("connections")
        End If

        If (variableDispenser Is Nothing) Then
            Throw New ArgumentNullException("variableDispenser")
        End If

        If (infoEvents Is Nothing) Then
            Throw New ArgumentNullException("infoEvents")
        End If

        Try
            If Me.myRootDirectorySourceValue = RootDirectorySource.ConnectionManager Then
                If Not connections.Contains(Me.rootDirectoryValue) Then
                    infoEvents.FireError(0, "ForEachDirectory", _
                        "The ConnectionManager " & Me.rootDirectoryValue _
                        & " does not exist in the collection.", "", 0)

                    Return DTSExecResult.Failure
                End If
            ElseIf Me.myRootDirectorySourceValue = RootDirectorySource.Variable Then
                If Not variableDispenser.Contains(Me.rootDirectoryValue) Then
                    infoEvents.FireError(0, "ForEachDirectory", _
                        "The Variable " & Me.rootDirectoryValue _
                        & " does not exist in the collection.", "", 0)

                    Return DTSExecResult.Failure
                End If
            End If

            ' Verify that a root directory is specified.
            If Me.rootDirectoryValue.Length < 1 Then
                infoEvents.FireError(0, "ForEachDirectory", _
                    "The RootDirectory is configured to use " _
                    & Me.myRootDirectorySourceValue.ToString() _
                    & " but no value is specified.", "", 0)
            End If

            Return DTSExecResult.Success

        Catch ex As Exception
            infoEvents.FireError(0, "ForEachDirectory", ex.Message, "", 0)
            Return DTSExecResult.Failure
        End Try
    End Function
#End Region

#Region "GetEnumerator"

    ' <summary>
    ' This method is called by the ForEachLoop container during execution. An object that implements IEnumerable
    ' must be returned, which the ForEachLoop then iterates, and provides the value of each object in the
    ' enumeratedValue variable that is available to the control flow contained by the For Each Loop.
    ' <summary>
    ' <param name="connections">The collection of ConnectionManagers in the package.<param>
    ' <param name="variableDispenser">A VariableDispenser used to read and write variables in the package.<param>
    ' <param name="events">Used to fire events.<param>
    ' <param name="log">Used to write log entries.<param>
    ' <returns>An object that implements IEnumerable.<returns>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
    Public Overrides Function GetEnumerator(ByVal connections As Connections, ByVal variableDispenser As VariableDispenser, ByVal events As IDTSInfoEvents, ByVal log As IDTSLogging) As Object
        If (connections Is Nothing) Then
            Throw New ArgumentNullException("connections")
        End If

        If (events Is Nothing) Then
            Throw New ArgumentNullException("events")
        End If

        If (variableDispenser Is Nothing) Then
            Throw New ArgumentNullException("variableDispenser")
        End If

        Try
            Dim rootDir As String = ""

            directories = New ArrayList()

            If Me.myRootDirectorySourceValue = RootDirectorySource.ConnectionManager Then
                rootDir = CStr(connections(Me.rootDirectoryValue).AcquireConnection(Nothing))
            ElseIf Me.RootDirectorySourceValue = RootDirectorySource.Variable Then '
                Dim vars As Variables = Nothing
                variableDispenser.LockOneForRead(Me.rootDirectoryValue, vars)
                rootDir = vars(Me.rootDirectoryValue).Value.ToString()
                vars.Unlock()
            Else
                rootDir = Me.rootDirectoryValue
            End If

            If rootDir.Length <> 0 And Directory.Exists(rootDir) Then
                AddDirectory(New DirectoryInfo(rootDir), Me.includeRootDirectoryValue)
                Return directories.GetEnumerator()
            Else
                events.FireError(0, "ForEachDirectory", "The RootDirectory is not provided or does not exist.", "", 0)
                Return vbNull
            End If

        Catch ex As Exception
            events.FireError(0, "ForEachDirectory", ex.Message, "", 0)
        End Try

        Return vbNull
    End Function
#End Region

#Region "IDTSComponentPersist Members"

    ' <summary>
    ' This method is called when the Enumerator is saved being saved. The Runtime provides a partial xml document
    ' that is used to persist information about the enumerator. For each of the properties of the
    ' enumerator an XmlElement is created and the value of the property is persisted in the
    ' InnerText property of th element.
    ' <summary>
    ' <param name="doc">The partial xml document provided by the runtime. After this method
    ' returns the DTS runtime incorporates the document into the package XML document<param>
    ' <param name="infoEvents">The IDTSInfoevents interface that can be used to provide information 
    ' during persistence.<param>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
    Public Sub SaveToXML(ByVal doc As System.Xml.XmlDocument, ByVal infoEvents As IDTSInfoEvents) Implements IDTSComponentPersist.SaveToXML
        If (doc Is Nothing) Then
            Throw New ArgumentNullException("doc")
        End If

        If (infoEvents Is Nothing) Then
            Throw New ArgumentNullException("infoEvents")
        End If

        Try
            Dim eForEachDir As XmlElement = doc.CreateElement("ForEachDirectory")

            Dim eDirectory As XmlElement = doc.CreateElement("RootDirectory")
            eDirectory.InnerText = Me.rootDirectoryValue
            eForEachDir.AppendChild(eDirectory)

            Dim eIncludeRootDir As XmlElement = doc.CreateElement("IncludeRootDirectory")
            eIncludeRootDir.InnerText = Me.includeRootDirectoryValue.ToString()
            eForEachDir.AppendChild(eIncludeRootDir)

            Dim eEnumerateSubFolders As XmlElement = doc.CreateElement("EnumerateSubFolders")
            eEnumerateSubFolders.InnerText = Me.enumerateSubFoldersValue.ToString()
            eForEachDir.AppendChild(eEnumerateSubFolders)

            Dim eRootDirectorySource As XmlElement = doc.CreateElement("RootDirectorySource")
            eRootDirectorySource.InnerText = Fix(Me.rootDirectorySourceValue).ToString(System.Globalization.CultureInfo.InvariantCulture)
            eForEachDir.AppendChild(eRootDirectorySource)

            Dim eSiblingFoldersBeforeSubFolders As XmlElement = doc.CreateElement("SiblingFoldersBeforeSubFolders")
            eSiblingFoldersBeforeSubFolders.InnerText = Me.siblingFoldersBeforeSubFoldersValue.ToString()
            eForEachDir.AppendChild(eSiblingFoldersBeforeSubFolders)

            doc.AppendChild(eForEachDir)
        Catch ex As Exception
            infoEvents.FireError(0, "ForEachDirectory", ex.Message, "", 0)
        End Try
    End Sub

    ' <summary>
    ' This method is called when the Enumerator is being loaded in a package. The runtime provides
    ' the XmlElement that is created during the SaveToXml method. The properties of the enumerator
    ' are initialized to the values persisted in the xml element.
    ' <summary>
    ' <param name="node">The XmlElement containing the enumerator information.<param>
    ' <param name="infoEvents">An IDTSInfoEvents that is used to provide information during serialization.<param>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")> _
    Public Sub LoadFromXML(ByVal node As System.Xml.XmlElement, ByVal infoEvents As IDTSInfoEvents) Implements IDTSComponentPersist.LoadFromXML
        If (infoEvents Is Nothing) Then
            Throw New ArgumentNullException("infoEvents")
        End If

        If (node Is Nothing) Then
            Throw New ArgumentNullException("node")
        End If

        Try
            Me.rootDirectoryValue = node("RootDirectory").InnerText
            Me.enumerateSubFoldersValue _
                = Convert.ToBoolean(node("EnumerateSubFolders").InnerText, _
                System.Globalization.CultureInfo.InvariantCulture)
            Me.includeRootDirectoryValue _
                = Convert.ToBoolean(node("IncludeRootDirectory").InnerText, _
                System.Globalization.CultureInfo.InvariantCulture)
            Me.siblingFoldersBeforeSubFoldersValue _
                = Convert.ToBoolean(node("SiblingFoldersBeforeSubFolders").InnerText, _
                System.Globalization.CultureInfo.InvariantCulture)
            Me.rootDirectorySourceValue _
                = CType(Convert.ToInt32(node("RootDirectorySource").InnerText, _
                System.Globalization.CultureInfo.InvariantCulture), _
                ForEachDirectory.RootDirectorySource)
        Catch ex As Exception
            infoEvents.FireError(0, "ForEachDirectory", ex.Message, "", 0)
        End Try
    End Sub

#End Region

#Region "AddDirectory"

    ' <summary>
    ' Adds the directories contained in the provided DirectoryInfo parameter. Based on the 
    ' settings of the enumerator, this method is called recursively to add the subfolders of the subfolders.
    ' <summary>
    ' <param name="dirInfoParent">The directory that is added to the directories array.<param>
    ' <param name="addParent">Specifies whether to add the DirectoryInfo to the list, or just the subfolders.<param>
    Private Sub AddDirectory(ByVal dirInfoParent As DirectoryInfo, ByVal addParent As Boolean)
        Dim dirInfoChildren As DirectoryInfo() = dirInfoParent.GetDirectories()

        ' Add the parent.
        If addParent Then
            directories.Add(dirInfoParent.FullName)
        End If

        ' Yes, add the subfolders
        Dim dirInfoChild As DirectoryInfo

        For Each dirInfoChild In dirInfoChildren
            directories.Add(dirInfoChild.FullName)

            If Me.enumerateSubFoldersValue And Not Me.siblingFoldersBeforeSubFoldersValue Then
                ' Subfolders of the subfolder, or the sibling folders and then subfolders?
                AddDirectory(dirInfoChild, False)
            End If
        Next

        ' Sibling folders before the subfolders folders, so now that the siblings
        ' have been added, add the children of the siblings.
        If Me.enumerateSubFoldersValue And Me.siblingFoldersBeforeSubFoldersValue Then
            'Dim dirInfoChild As DirectoryInfo
            For Each dirInfoChild In dirInfoChildren
                AddDirectory(dirInfoChild, False)
            Next dirInfoChild
        End If
    End Sub
#End Region
End Class